﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Net.Http.Formatting;

namespace ApimDelExample
{
    public static class APIMHandler
    {
        public static string ApimBaseUrl { get; set; }

        public static string ApimPrimaryKey { get; set; }

        public static string ApimIdentifier { get; set; }

        public static string ApiVersion { get; set; }



        /// <summary>
        /// Add a new user to the APIManager
        /// </summary>
        /// <param name="userID">The user identifier.</param>
        /// <param name="firstName">The first name.</param>
        /// <param name="lastName">The last name.</param>
        /// <param name="email">The email.</param>
        /// <param name="password">The password.</param>
        /// <param name="note">The note.</param>
        /// <returns>true if suucessful. False probably means a duplicate email</returns>
        /// <exception cref="System.Exception">some other error</exception>
        public static async Task<bool> AddNewUser(string userID, string firstName, string lastName, string email, string password, string note)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "users/" + userID.ToString() + "?api-version=" + ApiVersion;
            var response = await client.PutAsJsonAsync(uri, new { firstName = firstName, lastName = lastName, email = email, password = password, state = "active", note = note });
            if (response.StatusCode == System.Net.HttpStatusCode.Created)
            {
                return true;
            }
            else
            {
                var error = await response.Content.ReadAsStringAsync();
                return false;// could indicate duplicate userid or email - probably the latter
            }
            //throw new Exception(string.Format("Error in AddNewUser: return status {0}. Content: {1}", response.StatusCode.ToString(), error));
        }

        /// <summary>
        /// Checks the user exists.
        /// </summary>
        /// <param name="userID">The user identifier.</param>
        /// <returns>true if user exists</returns>
        /// <exception cref="System.Exception">some other error occurred</exception>
        public static async Task<bool> CheckUserExists(string userID)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "users/" + userID.ToString() + "?api-version=" + ApiVersion;
            var response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                return true;
            }
            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return false;
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in CheckUserExists: return status {0}. Content: {1}", response.StatusCode.ToString(), error));
        }

        /// <summary>
        /// Gets the sso identifier.
        /// </summary>
        /// <param name="userID">The user identifier.</param>
        /// <returns>A URL with token. Append "returnUrl=" + the local url of the login start and redirect</returns>
        /// <exception cref="System.Exception"></exception>
        public static async Task<string> GetSSOId(string userID)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "users/" + userID.ToString() + "/generateSsoUrl?api-version=" + ApiVersion;
            var response = await client.PostAsync(uri, null);
            if (response.IsSuccessStatusCode)
            {

                if (response.Content != null)
                {
                    var responseString = Json.Decode(await response.Content.ReadAsStringAsync()).value;
                    return responseString;
                }
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in GetSSOId: return status {0}. Content: {1}", response.StatusCode.ToString(), error));

        }

        public static async Task<List<string>> GetNonApprovalProductIds()
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "products?api-version=" + ApiVersion;
            var response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                var prodIds = new List<string>();
                if (response.Content != null)
                {
                    foreach (var product in Json.Decode(await response.Content.ReadAsStringAsync()).value)
                    {
                        if (!(product.approvalRequired ?? false))
                        {
                            prodIds.Add(product.id);
                        }
                    }
                }
                return prodIds;
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in GetNonApprovalProductIds: return status {0}. Content: {1}", response.StatusCode.ToString(), error));

        }

        public static async Task<string> GetSubscriptionFromUserAndProductIds(string userId, string productId)
        {
            //The REST API and the delegation are inconsistent in how they represent ids.
            if (!userId.StartsWith("/users/"))
                userId = "/users/" + userId;
            if (!productId.StartsWith("/products/"))
                productId = "/products/" + productId;

            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "subscriptions?api-version=" + ApiVersion;
            var response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                if (response.Content != null)
                {
                    foreach (var subs in Json.Decode(await response.Content.ReadAsStringAsync()).value)
                    {
                        if (subs.productId == productId && subs.userId == userId)
                        {
                            return ((string)subs.id).Substring(15); //remove "/subscriptions/" from start
                        }
                    }
                }
                return string.Empty;
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in GetSubscriptionFromUserAndProductIds: return status {0}. Content: {1}", response.StatusCode.ToString(), error));
        }

        public static async Task<bool> SubscribeToProduct(string subscriptionId, string UserId, string productId)
        {
            //The REST API and the delegation are inconsistent in how they represent ids.
            if (!UserId.StartsWith("/users/"))
                UserId = "/users/" + UserId;
            if (!productId.StartsWith("/products/"))
                productId = "/products/" + productId;

            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            var uri = ApimBaseUrl + "subscriptions/" + subscriptionId.ToString() + "?api-version=" + ApiVersion;
            var response = await client.PutAsJsonAsync(uri, new { userId = UserId, productId = productId, state = "active" });
            if (response.StatusCode == System.Net.HttpStatusCode.Created)
            {
                return true;
            }
            if (response.StatusCode == System.Net.HttpStatusCode.Conflict)
            {
                return false;
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in SubscribeToProduct: return status {0}. Content: {1}", response.StatusCode.ToString(), error));

        }

        /// <summary>
        /// Updates the state of the subscription.
        /// </summary>
        /// <param name="subscriptionId">The subscription identifier.</param>
        /// <param name="newState">The new state. Choices are active, suspended, submitted, rejected, cancelled, expired.</param>
        /// <returns></returns>
        /// <exception cref="System.Exception"></exception>
        public static async Task<bool> UpdateSubscriptionState(string subscriptionId, string newState)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "SharedAccessSignature " + CalculateAcessToken(ApimIdentifier, ApimPrimaryKey));
            client.DefaultRequestHeaders.TryAddWithoutValidation("If-Match", "\"*\"");
            var uri = ApimBaseUrl + "subscriptions/" + subscriptionId + "?api-version=" + ApiVersion;
            var response = await client.PatchAsJsonAsync(uri, new { state = newState });
            if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
            {
                return true;
            }
            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return false;
            }
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception(string.Format("Error in SubscribeToProduct: return status {0}. Content: {1}", response.StatusCode.ToString(), error));

        }

        public static bool CheckHash(string message, string sid, string ApimDelegationSecret)
        {
            using (var hmacsha512 = new HMACSHA512(Encoding.UTF8.GetBytes(ApimDelegationSecret)))
            {
                byte[] hashmessage = hmacsha512.ComputeHash(Encoding.UTF8.GetBytes(message));
                return Convert.ToBase64String(hashmessage) == sid;
            }
        }

        private static string CalculateAcessToken(string id, string key)
        {
            var expiry = DateTime.UtcNow.AddDays(10);
            using (var encoder = new HMACSHA512(Encoding.UTF8.GetBytes(key)))
            {
                var dataToSign = id + "\n" + expiry.ToString("O", CultureInfo.InvariantCulture);
                var hash = encoder.ComputeHash(Encoding.UTF8.GetBytes(dataToSign));
                var signature = Convert.ToBase64String(hash);
                var encodedToken = string.Format("uid={0}&ex={1:o}&sn={2}", id, expiry, signature);
                return encodedToken;
            }
        }

        public static Task<HttpResponseMessage> PatchAsJsonAsync<T>(this HttpClient client, string requestUri, T value)
        {
            var content = new ObjectContent<T>(value, new JsonMediaTypeFormatter());
            var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri) { Content = content };

            return client.SendAsync(request);
        }



    }
}